/*******************************************************************************
 * Copyright (c) 2013 QPark Consulting  S.a r.l.
 * 
 * This program and the accompanying materials are made available under the 
 * terms of the Eclipse Public License v1.0. 
 * The Eclipse Public License is available at 
 * http://www.eclipse.org/legal/epl-v10.html.
 * 
 * Contributors:
 *     Bernhard Hausen - Initial API and implementation
 *
 ******************************************************************************/
package com.samples.platform.core.security;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;

import com.qpark.eip.core.ReInitalizeable;
import com.qpark.eip.core.spring.ApplicationPlaceholderConfigurer;
import com.qpark.eip.core.spring.security.EipUserProvider;

/**
 * @author bhausen
 */
public class UserProvider implements EipUserProvider, ReInitalizeable {
	static class UserDefinition {
		private String name;
		private String password = "";
		private final TreeSet<String> rolenames = new TreeSet<String>();

		public void addRolename(final String rolename) {
			if (rolename.startsWith("ROLE_")) {
				this.rolenames.add(rolename);
			}
		}

		/**
		 * @return the name.
		 */
		public String getName() {
			return this.name;
		}

		/**
		 * @return the password.
		 */
		public String getPassword() {
			return this.password;
		}

		/**
		 * @return the roles.
		 */
		public List<GrantedAuthority> getRoles() {
			List<GrantedAuthority> roles = new ArrayList<GrantedAuthority>();
			for (String rolename : this.rolenames) {
				roles.add(new SimpleGrantedAuthority(rolename));
			}
			return roles;
		}

		/**
		 * @param name
		 *            the name to set.
		 */
		public void setName(final String name) {
			this.name = name;
		}

		/**
		 * @param password
		 *            the password to set.
		 */
		public void setPassword(final String password) {
			this.password = password;
		}

		public int size() {
			return this.rolenames.size();
		}

		@Override
		public String toString() {
			StringBuffer sb = new StringBuffer(512);
			sb.append("User \"").append(this.name).append("\" with roles ");
			int i = 0;
			for (String rolename : this.rolenames) {
				if (i > 0) {
					sb.append(", ");
				}
				sb.append(rolename);
				i++;
			}
			return sb.toString();
		}
	}
	/**
	 * @param s
	 *            The property name.
	 * @return the user name key part.
	 */
	private static String extractUser(final String s) {
		String userName = "";
		userName = s.substring(BUS_PROPERTY_NAME_START.length(), s.length());
		userName = userName.substring(0, userName.indexOf('.'));
		return userName;
	}

	/** The {@link Logger}. */
	private final org.slf4j.Logger logger = org.slf4j.LoggerFactory
			.getLogger(UserProvider.class);

	/** The {@link ApplicationPlaceholderConfigurer}. */
	@Autowired
	private ApplicationPlaceholderConfigurer properties;

	private static final String BUS_PROPERTY_NAME_START = "bus.user.definition.";
	/** The name of the anonymous role. */
	public static final String ROLE_ANONYMOUS = "ROLE_ANONYMOUS";
	/** The name of the common role. */
	public static final String ROLE_COMMON = "ROLE_COMMON";

	/** The map containing the users of the application. */
	private Map<String, User> userMap = null;

	/** Create the users of the service bus out of the properties. */
	public void createUsers() {
		this.logger.debug("+createUsers");
		this.userMap = new HashMap<String, User>();
		TreeMap<String, String> map = new TreeMap<String, String>();
		for (Map.Entry<String, String> entry : this.properties.getProperties()
				.entrySet()) {
			if (entry.getKey() != null
					&& entry.getKey().startsWith(BUS_PROPERTY_NAME_START)) {
				map.put(entry.getKey(), entry.getValue());
			}
		}
		UserDefinition userDefinition = new UserDefinition();
		String userKey = null;
		for (Map.Entry<String, String> entry : map.entrySet()) {
			if (userKey != null && !userKey.equals(extractUser(entry.getKey()))) {
				User user = this.getUser(userDefinition);
				this.userMap.put(userDefinition.getName(), user);
				userDefinition = new UserDefinition();
			}
			userKey = extractUser(entry.getKey());
			if (entry.getKey().endsWith("userName")) {
				userDefinition.setName(entry.getValue());
			} else if (entry.getKey().endsWith("password")) {
				userDefinition.setPassword(entry.getValue());
			} else if (entry.getKey().contains(".role.")) {
				if (entry.getValue() != null
						&& !entry.getValue().equals(ROLE_ANONYMOUS)
						&& !entry.getValue().equals(ROLE_COMMON)) {
					userDefinition.addRolename(entry.getValue());
				}
			}
		}
		if (userDefinition.getName() != null) {
			User user = this.getUser(userDefinition);
			this.userMap.put(userDefinition.getName(), user);
			userDefinition = null;
		}
		this.logger.debug("-createUsers");
	}

	/**
	 * @see com.qpark.eip.core.spring.security.EipUserProvider#getUser(java.lang.String)
	 */
	@Override
	public User getUser(final String username) {
		// if (this.userMap == null) {
		this.createUsers();
		// }
		return this.userMap.get(username);
	}

	/**
	 * Add the user from the userDefinition to the map.
	 * 
	 * @param userDefinition
	 */
	private User getUser(final UserDefinition userDefinition) {
		/* Only if the userdefinition contains roles add ROLE_COMMON. */
		/* Bug 1604: ROLE_COMMON is not automatically added any more. */
		// if (userDefinition.size() > 0) {
		// userDefinition.addRolename(ROLE_COMMON);
		// }
		userDefinition.addRolename(ROLE_ANONYMOUS);
		boolean enabled = true;
		boolean accountNonExpired = true;
		boolean credentialsNonExpired = true;
		boolean accountNonLocked = true;
		User user = new PlatformUser(userDefinition.getName(),
				userDefinition.getPassword(), enabled, accountNonExpired,
				credentialsNonExpired, accountNonLocked,
				userDefinition.getRoles());
		this.logger.info("Created: {}", userDefinition.toString());
		return user;
	}

	/**
	 * @see com.qpark.eip.core.ReInitalizeable#reInitalize()
	 */
	@Override
	public void reInitalize() {
		this.userMap = null;
	}

}
